package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.constant.CacheConstants;
import com.zzyl.constant.SuperConstant;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.*;
import com.zzyl.service.AlarmRuleService;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import com.zzyl.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AlarmRuleServiceImpl implements AlarmRuleService {

    @Autowired
    private AlarmRuleMapper alarmRuleMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private NursingElderMapper nursingElderMapper;

    @Autowired
    private SysUserMapper sysUserRoleMapper;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private AlertDataMapper alertDataMapper;


    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 创建报警规则
     *
     * @param alertRuleDto
     */
    @Override
    public void create(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = new AlertRule();
        BeanUtils.copyProperties(alertRuleDto, alertRule);
        alarmRuleMapper.create(alertRule);
    }

    /**
     * 分页查询报警规则
     *
     * @param pageNum
     * @param pageSize
     * @param alertRuleName
     * @param functionName
     * @param productKey
     * @return
     */
    @Override
    public PageResponse<AlertRuleVo> page(Integer pageNum, Integer pageSize, String alertRuleName, String functionName, String productKey) {
        // 分页
        PageHelper.startPage(pageNum, pageSize);
        // 查询数据
        Page<AlertRuleVo> alertRules = alarmRuleMapper.getPage(alertRuleName, functionName, productKey);
        // 封装分页数据
        PageResponse<AlertRuleVo> pageResponse = new PageResponse<>();
        pageResponse.setTotal(alertRules.getTotal());
        pageResponse.setPage(pageNum);
        pageResponse.setPageSize(pageSize);
        pageResponse.setRecords(alertRules.getResult());

        pageResponse.getRecords().forEach(item -> {
            String rules = item.getFunctionName() + item.getOperator() + item.getValue();
            item.setRules(rules);
        });
        // 返回
        return pageResponse;
    }

    /**
     * 根据ID查询报警规则
     *
     * @param id
     * @return
     */
    @Override
    public AlertRuleVo read(Integer id) {
        AlertRuleVo Vo = alarmRuleMapper.read(id);
        return Vo;
    }

    /**
     * 更新报警规则
     *
     * @param id
     * @param alertRuleDto
     */
    @Override
    public void update(Long id, AlertRuleDto alertRuleDto) {
        AlertRule alertRule = new AlertRule();
        BeanUtils.copyProperties(alertRuleDto, alertRule);
        alertRule.setUpdateTime(LocalDateTime.now());
        alertRule.setId(id);
        alarmRuleMapper.update(alertRule);
    }

    /**
     * 删除报警规则
     *
     * @param id
     */
    @Override
    @Transactional
    public void delete(Integer id) {
        alarmRuleMapper.delete(id);
    }

    /**
     * 启用-禁用
     *
     * @param id
     * @param status
     */
    @Override
    public void updateStatus(Long id, Integer status) {
        alarmRuleMapper.updateStatus(id, status);
    }

    @Override
    public void alertFilter(DeviceDataVo vo) {
        //1. 判断上报的时间是否已经过去太久，如果太久了，那么就属于挤压得消息，挤压得消息不处理
        if (LocalDateTimeUtil.between(vo.getAlarmTime(), LocalDateTime.now(), ChronoUnit.SECONDS) > 60) {
            //第一个参数写得是小得时间，第二个擦书写的是大的时间，第三个参数写的是时间单位
            return;
        }

        //2.通过产品的functionID 找到产品的校验规则(全局设备的)
        List<AlertRule> ruleList = alarmRuleMapper.queryAlarm(vo.getFunctionId(), null, vo.getProductKey());
        ruleList = CollUtil.isEmpty(ruleList) ? new ArrayList<>() : ruleList;

        //3.通过设备的iotId和functionId找到设备的校验规则
        List<AlertRule> deviceRuleList = alarmRuleMapper.queryAlarm(vo.getFunctionId(), vo.getIotId(), null);

        //4.合并所有的规则 然后去重
        ruleList.addAll(deviceRuleList);
        ruleList = ruleList.stream().distinct().collect(Collectors.toList());

        //5.判断规则是否为空，如果为空则遍历下一个规则，继续校验数据
        if (CollUtil.isEmpty(ruleList)) {
            return;
        }

        //6. 遍历下一个规则，然后用当前的规则校验数据
        ruleList.forEach(rule -> {
            deviceDateAlarmHandler(rule, vo);
        });
    }
    /*
            # 　　　┏┓　　　┏┓
            # 　　┏┛┻━━━┛┻┓━━┓
            # 　　┃　　　　　  ┃
            # 　　┃　　　━　　 ┃
            # 　　┃　┳┛　┗┳　 ┃
            # 　　┃　　　　　　┃
            # 　　┃　　　┻　　 ┃
            # 　　┃　　　　　 ┃
            # 　　┗━┓　　　┏━┛
           # 　　　　┃　　　┃
           # 　　　　┃　　　┃
           # 　　　　┃　　　┗━━━┓
           # 　　　　┃　　　　　 ┣┓
           # 　　　　┃　　　　 ┏┛
           # 　　　　┗┓┓┏━┳┓┏┛
           #  　　　　┃┫┫　┃┫┫
           # 　　　　 ┗┻┛　┗┻┛
        */

    /**
     * 根据规则校验数据
     *
     * @param rule
     * @param vo
     */
    private void deviceDateAlarmHandler(AlertRule rule, DeviceDataVo vo) {
        //1. 判断数据上报的时间(alarmTime) 是否在规则校验的时间之内，如果不在则返回
        //根据数据库看到字段，以~分割
        String alertEffectivePeriod = rule.getAlertEffectivePeriod();
        String[] split = alertEffectivePeriod.split("~");
        //讲时间格式全部转换为LocalTime形式
        LocalTime startTime = LocalTime.parse(split[0]);
        LocalTime endTime = LocalTime.parse(split[1]);
        LocalTime alarmTime = LocalDateTimeUtil.of(vo.getAlarmTime()).toLocalTime();
        if (alarmTime.isBefore(startTime) || alarmTime.isAfter(endTime)) {
            return;
        }

        //2. 判断上报的数据是否达到了规则阈值，如果没有则删除redis中储存的连续次数
        //key: 前缀 + 产品的key +iotId + functionId
        String countKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + ":" + vo.getProductKey() + ":" + vo.getIotId() + ":" + vo.getFunctionId();
        int result = NumberUtil.compare(rule.getValue(), Double.parseDouble(vo.getDataValue()));
        //比较两个值的大小
        //x==y返回0，x<y返回小于0的数，x>y返回大于0的数
        if ((">=".equals(rule.getOperator()) && result > 0) || ("<".equals(rule.getOperator()) && result <= 0)) {

            redisTemplate.delete(countKey);
            return;
        }


        //3. 如果达到了阈值，则判断是否有沉默周期，如果有则返回，如果没有则继续校验
        String silentKey = CacheConstants.ALERT_SILENT_PREFIX + ":" + vo.getProductKey() + ":" + vo.getIotId() + ":" + vo.getFunctionId();
        String silentValue = redisTemplate.opsForValue().get(silentKey);
        if (ObjectUtil.isNotEmpty(silentValue)){
            return;
        }

        //4. 从redis中获取当前这个唱片的这个设备的这个维度的连续告警次数
        String countValue = redisTemplate.opsForValue().get(countKey);

        //5. 判断是否为空，如果为空则设置0，然后+1，不为空则在原本基础上+1
        int count = ObjectUtil.isEmpty(countValue) ? 0 : Integer.parseInt(countValue);
        count++;
        //6. 判断是否达到持续周期设置的值，如果没有达到阈值，则重新写入redis
        if (count < rule.getDuration()) {
            redisTemplate.opsForValue().set(countKey, count + "");
            return;
        }

        //设置静默值
        redisTemplate.opsForValue().set(silentKey, rule.getAlertSilentPeriod() + "", rule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //8. 查询维修人员或护工
        List<Long> ids = new ArrayList<>();
        if (vo.getLocationType().equals(SuperConstant.DATA_STATE_1)) {
            //查询维修人员
            ids = sysUserRoleMapper.selectUserIdByRoleName("维修工");
        } else {
            if (vo.getPhysicalLocationType().equals("-1")) {
                //版定在老人身上的随身设备
                ids = deviceMapper.selectNursingIdByIotId(vo.getIotId());
            }else {
                //绑定的是床位
                ids=deviceMapper.selectNursingIdByBedAndIotId(vo.getIotId());
            }
        }

        //9. 查询管理员，并合并两个数据
        List<Long> superIds = sysUserRoleMapper.selectUserIdByRoleName("超级管理员");
        ids.addAll(superIds);
        //10. 批量插入告警数据
        List<AlertData> list = insertAlertData(vo,rule,ids);
        //11. 通知对应的工作人员
        websocketNotify(rule, list.get(0), ids);

    }

    private void websocketNotify(AlertRule rule, AlertData alertData, List<Long> ids) {
        AlertNotifyDto dto = new AlertNotifyDto();
        BeanUtils.copyProperties(alertData,dto);

        dto.setFunctionName(rule.getFunctionName());
        dto.setAlertDataType(alertData.getType());
        dto.setNotifyType(1);

        webSocketServer.sendMessageToConsumer(dto,ids);

    }

    private List<AlertData> insertAlertData(DeviceDataVo deviceDataVo, AlertRule alertRule, List<Long> consumerIds) {
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRule.getFunctionName(), alertRule.getOperator(), alertRule.getValue(), alertRule.getDuration());
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        alertData.setAlertRuleId(alertRule.getId());
        alertData.setAlertReason(alertReason);
        alertData.setType(alertRule.getAlertDataType());
        alertData.setStatus(0);

        List<AlertData> list = consumerIds.stream().map(id -> {
            AlertData dbAlertData = BeanUtil.toBean(alertData, AlertData.class);
            dbAlertData.setUserId(id);
            return dbAlertData;
        }).collect(Collectors.toList());
        alertDataMapper.batchInsert(list);
        return list;
    }
}
